Skip to content

Alloc + no_std: part 1#641

Merged
bal-e merged 2 commits intoNLnetLabs:mainfrom
soywod:alloc-part-1
Apr 29, 2026
Merged

Alloc + no_std: part 1#641
bal-e merged 2 commits intoNLnetLabs:mainfrom
soywod:alloc-part-1

Conversation

@soywod
Copy link
Copy Markdown
Contributor

@soywod soywod commented Apr 21, 2026

The no_std and alloc features lack of consistency. A user should be able to StreamTarget::new_vec() without std but alloc features, which is not the case at the moment. I would like to help, but there is a lot to change. I propose to go by step. Here a first PR that:

  • Uses impl core::error instead of std::error, and remove the std feature gate
  • Replaced extern crate std; by a proper cfg_attr on no_std
  • Removed duplicate declare_error_trait!

Refs: #572, NLnetLabs/octseq#68

Copy link
Copy Markdown
Contributor

@bal-e bal-e left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @soywod! I happen to know your work on Himalaya :)

Thanks for making a PR, we're totally on board with the direction here. And thanks for breaking it down into digestible chunks. We're happy to support this use case, and we hope that Domain can serve your needs! FYI we're slowly overhauling the API with domain::new, if it already has all the functionality you need then I would suggest trying it out over the new API.

FYI, we're releasing a new major version of Domain shortly (to 0.12), and this PR will likely be merged afterwards. We're happy to make a patch release afterwards if you'd like this PR (and perhaps follow-up ones) in a crates.io release; just let us know.

Comment thread src/lib.rs Outdated
@soywod
Copy link
Copy Markdown
Contributor Author

soywod commented Apr 23, 2026

Thanks for making a PR, we're totally on board with the direction here. And thanks for breaking it down into digestible chunks. We're happy to support this use case, and we hope that Domain can serve your needs!

These are great news, happy to hear that the direction is fine. I plan to use Domain for PIM service discovery, I recently dived into and found that it plays nicely with Pimalaya I/O-free lib pattern.

FYI we're slowly overhauling the API with domain::new, if it already has all the functionality you need then I would suggest trying it out over the new API.

I just tried it, it fits even better. I will go for it! Is it OK for me to prioritize the alloc refactor on new, so I can use it as possible?

FYI, we're releasing a new major version of Domain shortly (to 0.12), and this PR will likely be merged afterwards. We're happy to make a patch release afterwards if you'd like this PR (and perhaps follow-up ones) in a crates.io release; just let us know.

Sure, a patch would be wonderful.


With the new API, I somehow cannot make DNS query over TCP. I suspect a misusage from my side, but I don't know enough DNS domain to understand why. I just followed the example given in new base inline doc. In the regular API, StreamTarget had a as_dgram_slice and as_stream_slice:

impl<Target: AsRef<[u8]>> StreamTarget<Target> {
/// Returns an octets slice of the message for stream transports.
///
/// The slice will start with the length octets and can be send as is
/// through a stream transport such as TCP.
pub fn as_stream_slice(&self) -> &[u8] {
self.target.as_ref()
}
/// Returns an octets slice of the message for datagram transports.
///
/// The slice will not contain the length octets but only the actual
/// message itself. This slice can be used for sending via datagram
/// transports such as UDP.
pub fn as_dgram_slice(&self) -> &[u8] {
&self.target.as_ref()[2..]
}
}

Any idea?

@bal-e
Copy link
Copy Markdown
Contributor

bal-e commented Apr 24, 2026

Thanks for making a PR, we're totally on board with the direction here. And thanks for breaking it down into digestible chunks. We're happy to support this use case, and we hope that Domain can serve your needs!

I just tried it, it fits even better. I will go for it! Is it OK for me to prioritize the alloc refactor on new, so I can use it as possible?

Good to hear! Yes, feel free to prioritize new.

Sure, a patch would be wonderful.

Great, we'll make a release soon.

With the new API, I somehow cannot make DNS query over TCP. I suspect a misusage from my side, but I don't know enough DNS domain to understand why. I just followed the example given in new base inline doc. In the regular API, StreamTarget had a as_dgram_slice and as_stream_slice:

impl<Target: AsRef<[u8]>> StreamTarget<Target> {
/// Returns an octets slice of the message for stream transports.
///
/// The slice will start with the length octets and can be send as is
/// through a stream transport such as TCP.
pub fn as_stream_slice(&self) -> &[u8] {
self.target.as_ref()
}
/// Returns an octets slice of the message for datagram transports.
///
/// The slice will not contain the length octets but only the actual
/// message itself. This slice can be used for sending via datagram
/// transports such as UDP.
pub fn as_dgram_slice(&self) -> &[u8] {
&self.target.as_ref()[2..]
}
}

Any idea?

Ah yes! When sending DNS messages over TCP, each message must be preceded by its length (big-endian u16, in units of bytes). StreamTarget does this for you automatically, but new base doesn't. I'll mention this in the docs on for the new-base MessageBuilder. In short, you would call MessageBuilder::new(&mut buffer[2..], ...), build the message as usual, then set the length prefix as buffer[0..2].copy_from_slice(&(message_len as u16).to_be_bytes());. You can calculate the message length as message.as_bytes().len() using the AsBytes trait.

@soywod soywod requested a review from bal-e April 28, 2026 22:31
Copy link
Copy Markdown
Contributor

@bal-e bal-e left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM :) Thanks for contributing!

@bal-e bal-e merged commit 1646a78 into NLnetLabs:main Apr 29, 2026
15 checks passed
bal-e added a commit that referenced this pull request Apr 29, 2026
@soywod soywod deleted the alloc-part-1 branch April 29, 2026 13:30
@soywod
Copy link
Copy Markdown
Contributor Author

soywod commented Apr 29, 2026

Ah yes! When sending DNS messages over TCP, each message must be preceded by its length (big-endian u16, in units of bytes). StreamTarget does this for you automatically, but new base doesn't. I'll mention this in the docs on for the new-base MessageBuilder. In short, you would call MessageBuilder::new(&mut buffer[2..], ...), build the message as usual, then set the length prefix as buffer[0..2].copy_from_slice(&(message_len as u16).to_be_bytes());. You can calculate the message length as message.as_bytes().len() using the AsBytes trait.

Thanks for your help, I made it work using the following key steps:

  • Pass &mut buf[2..] to MessageBuilder::new()

  • Copy message length at the begining of the buffer:

    let msg: &mut Message = builder.finish();
    let msg_len = msg.as_bytes().len() as u16;
    buf[0..2].copy_from_slice(&msg_len.to_be_bytes());
  • Write the buffer to the stream (and not the message bytes) stream.write_all(&buf)

  • Parse response message without length prefix MessageParser::new(&buf[2..n])

@soywod soywod mentioned this pull request Apr 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants